热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

时会|句子_schedule与CFS算法

篇首语:本文由编程笔记#小编为大家整理,主要介绍了schedule与CFS算法相关的知识,希望对你有一定的参考价值。一、调度类与调

篇首语:本文由编程笔记#小编为大家整理,主要介绍了schedule与CFS算法相关的知识,希望对你有一定的参考价值。



一、调度类与调度实体



调度类是系统为了对不同进程调度进行区分而用的数据结构,其中记录了关于调度不同进程所需要的函数,每种调度算法都有其自己的调度类






调度实体是每个进程都有的数据结构,其中记录了调度此进程所需要使用的所有信息,且普通进程和实时进程有着不同的调度实体






关于不同种类的进程,主要是3种,普通进程、实时进程以及空闲进程(idle)


#define SCHED_NORMAL 0 /* 普通进程调度策略,即CFS算法 */
#define SCHED_FIFO 1 /* 实时进程的调度策略 */
#define SCHED_RR 2 /* 实时进程的调度策略 */
#define SCHED_BATCH 3
#define SCHED_IDLE 5 /* 空闲进程 */
#define SCHED_RESET_ON_FORK 0x40000000

SCHED_FIFO代表先入先出的调度算法:不使用时间片,一旦一个此种调度策略的进程处于可执行状态就会一直执行,直到它自己受阻塞或者释放处理器,只有更高级的SCHED_FIFO和SCHED_RR任务能够抢占它。

SCHED_RR代表实时轮流调度算法,与前者大体相同,只是其中每个任务都有着时间片,当耗完了时间片就不能再继续执行,时间片耗尽后同优先级的其他实时进程会被轮流调度。

SCHED_NORMAL代表普通进程的调度策略,我看的是2.6.32版本的内核代码,使用的是CFS(完全公平调度算法)。当系统中有实时进程时,普通进程是不会被调度执行的,只有当实时进程都结束以后才开始运用此调度算法选择普通进程运行


二、schedule函数主要流程



schedule()是进程调度的主要入口,作用是选择下一个运行的进程,且它在每次的进程选择时会找到最高优先级的调度类,每个调度类都有其自己的就绪队列,然后再从其中找出下一个该运行的进程。


首先声明了调度中需要用到的一些数据结构

asmlinkage void __sched schedule(void)
struct task_struct *prev, *next;
unsigned long *switch_count;
struct rq *rq;
int cpu;

禁止内核抢占,获取当前cpu的编号,获取当前cpu对应的就绪队列rq,当前进程的描述符prev,获取当前进程的切换次数switch_count,释放大内核锁,完成对时间调试的检查与统计

preempt_disable();
cpu = smp_processor_id();
rq = cpu_rq(cpu);
rcu_sched_qs(cpu);
prev = rq->curr;
switch_count = &prev->nivcsw;
release_kernel_lock(prev);
hedule_debug(prev);
if (sched_feat(HRTICK))
hrtick_clear(rq);

对当前的就绪队列锁上自旋锁,然后更新就绪队列上的时钟,清除当前进程的重新调度标志 TIF_NEED_RESCHED

spin_lock_irq(&rq->lock);
update_rq_clock(rq);
clear_tsk_need_resched(prev);

判断如果进程不是TASK_RUNNING(state>0)且此调度函数不是由于抢占而调用执行的(即当前进程不是被抢占的),那么如果当前进程仍有未处理信号就将其状态置为TASK_RUNNING,在后面会重新将其插入就绪队列中,否则将清除出就绪队列(此种情况应该是当前进程执行结束,所以自然要出就绪队列)

if (prev->state && !(preempt_count() & PREEMPT_ACTIVE))

if (unlikely(signal_pending_state(prev->state, prev)))
prev->state = TASK_RUNNING;
else
deactivate_task(rq, prev, 1);
switch_count = &prev->nvcsw;

判断如果当前cpu的就绪队列中没有了可运行的进程,那么调用负载均衡,从另一个较忙的cpu的就绪队列中拉一个进程过来执行

pre_schedule(rq, prev);
if (unlikely(!rq->nr_running))
idle_balance(cpu, rq);

将prev重新插入到就绪队列中的合适位置,对应之前的判断是否抢占的语句,之后执行pick_next_task函数来获得下一个要运行的进程
这里的两个函数都是钩子函数,由于不同的调度类有着不同的运行队列,所以要区分出prev是插入到哪种进程的就绪队列,下一个进程是调用实时进程还是普通进程

put_prev_task(rq, prev);
next = pick_next_task(rq);

判断选中的下一个要运行的进程是否是上一个进程,如果不是就更新进程描述符的相关字段,然后调用context_switch进行上下文切换(在此函数中会解开之前上的就绪队列的自旋锁),如果就是上一个进程的话,就不用麻烦了,直接解开就绪队列的自旋锁就行了

if (likely(prev != next))
sched_info_switch(prev, next);
perf_event_task_sched_out(prev, next, cpu);
rq->nr_switches++;
rq->curr = next;
++*switch_count;
context_switch(rq, prev, next);
cpu = smp_processor_id();
rq = cpu_rq(cpu);
else
spin_unlock_irq(&rq->lock);

这里的句子不懂是在干什么,其中有检查内核锁,解开内核抢占,判断是否有重新调度标志

post_schedule(rq);
if (unlikely(reacquire_kernel_lock(current) <0))
goto need_resched_nonpreemptible;
preempt_enable_no_resched();
if (need_resched())
goto need_resched;

三、CFS算法



CFS即完全公平调度算法&#xff0c;在2.6.23的内核版本中代替了原来的O(1)调度算法


实际上CFS算法不像以往有着时间片的概念&#xff0c;取而代之的是时间片的使用比&#xff0c;任何进程所获得的处理器时间都是他自己和其他所有可运行进程静态优先级值的相对值所决定的&#xff0c;而不像以往是静态优先级有着固定的算法来分配时间片



公式&#xff1a;分配给进程的运行时间&#61;调度周期*&#xff08;进程的权重值/所有进程总权重值&#xff09;


其中权重是由nice值决定的,内核中有prio_to_weight数组来查找不同nice值的权重(nice的值可用ps -el命令看到&#xff0c;其中的NI字段即nice值)。

static const int prio_to_weight[40] &#61;
/*-20*/ 88761, 71755, 56483, 46273, 36291,
/*-15*/ 29154, 23254, 18705, 14949, 11916,
/*-10*/ 9548, 7620, 6100, 4904, 3906,
/*-5*/ 3121, 2501, 1991, 1586, 1277,
/*0*/ 1024, 820, 655, 526, 423,
/*5*/ 335, 272, 215, 172, 137,
/*10*/ 110, 87, 70, 56, 45,
/*15*/ 36, 29, 23, 18, 15,
;

在之前的O(1)算法中通过进程的优先级来选择下一个要运行的进程&#xff0c;而在CFS算法中则是通过对vruntime(虚拟运行时间)的比较来选择下一个进程&#xff0c;vruntime最小的最先执行

vruntime通过task_new_fair函数中的update_curr()以及plaec_entity()计算出来&#xff0c;记录了一个进程到底运行了多长时间以及它还应该在运行多久

关于CFS算法&#xff0c;还有个很重要的就是它如果找到最小vruntime的进程&#xff0c;其利用的是红黑树



红黑树&#xff08;Red Black Tree&#xff09; 是一种自平衡二叉查找树&#xff0c;是在计算机科学中用到的一种数据结构。
红黑树在进行插入和删除操作时通过特定操作保持二叉查找树的平衡&#xff0c;从而获得较高的查找性能&#xff0c;它可以在O(log n)时间内做查找&#xff0c;插入和删除&#xff0c;这里的n 是树中元素的数目。


如图中就是一个红黑树&#xff0c;其中节点中的是进程的vruntime减去就绪队列的min_vruntime的值&#xff0c;此时调度的话就会选择最左边的节点的进程运行

但是新的进程是如何插入到红黑树中的呢&#xff1f;
实际上在进程被新建出来插入就绪队列时就已经完成了计算其的vruntime&#xff0c;并且在将其插入红黑树时就会判定新加入的进程的位置&#xff0c;如果它被放到了最左边&#xff0c;则在CFS算法的就绪队列的数据结构中有一个名为rb_leftmost的指针就会指向它&#xff0c;当执行调度函数选择下一个进程时&#xff0c;就会直接找到rb_leftmost所指向的进程&#xff0c;从而免去了查找的时间

一个新的进程插入红黑树的大致过程&#xff1a;
首先是do_fork中调用的wake_up_new_task()函数&#xff0c;其中执行了p->sched_class->task_new(rq,p)&#xff0c;即通过不同的调度类来将进程插入适当的就绪队列

void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
...
if (!p->sched_class->task_new || !current->se.on_rq)
activate_task(rq, p, 0);
else
p->sched_class->task_new(rq, p);
inc_nr_running(rq);

...

CFS的调度类fair_sched_class中task_new对应的是task_new_fair函数&#xff0c;其中通过update_curr()与place_entity()计算vruntime的值&#xff0c;最后调用enqueue_task_fair()将新的进程插入到普通进程的就绪队列中

static void task_new_fair(struct rq *rq, struct task_struct *p)

...
update_curr(cfs_rq);
...
place_entity(cfs_rq, se, 1);
...
enqueue_task_fair(rq, p, 0);

在enqueue_task_fair()中调用enqueue_entity&#xff0c;不过在enqueue_entity中实际调用的是__enqueue_entity来实现红黑树的插入
在__enqueue_entity主要就是一个while循环&#xff0c;它先将leftmost置为1&#xff0c;当新进程的比较节点的key值时&#xff0c;小于即向左走&#xff0c;leftmost不变&#xff0c;否则就是向右走&#xff0c;使leftmost&#61;0&#xff0c;即此进程不是红黑树中下次调度要选择的进程


static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)/*将一个实体(进程)插入到红黑树中*/
struct rb_node **link &#61; &cfs_rq->tasks_timeline.rb_node;
struct rb_node *parent &#61; NULL;
struct sched_entity *entry;
s64 key &#61; entity_key(cfs_rq, se);
int leftmost &#61; 1;
while (*link)
parent &#61; *link;
entry &#61; rb_entry(parent, struct sched_entity, run_node);
if (key link &#61; &parent->rb_left; /*红黑树的比较中向左走*/
else /*红黑树的比较中向右走&#xff0c;leftmost&#61;0*/
link &#61; &parent->rb_right;
leftmost &#61; 0;



推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 设计模式——模板方法模式的应用和优缺点
    本文介绍了设计模式中的模板方法模式,包括其定义、应用、优点、缺点和使用场景。模板方法模式是一种基于继承的代码复用技术,通过将复杂流程的实现步骤封装在基本方法中,并在抽象父类中定义模板方法的执行次序,子类可以覆盖某些步骤,实现相同的算法框架的不同功能。该模式在软件开发中具有广泛的应用价值。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
传导网络
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有